Persistent L1 Writer
Introduction
The persistent L1 writer is the component that provides a stateful interface to
simplify the coordination of the multiple L1Writer
instances.
Use case diagram
- The user (wallet service) calls
embedData
with arbitrary data to embed (e.g. an L2-transaction) and the service replies with auuid
. - The user can use the
uuid
to check the status of the transaction:- PENDING: the transaction has not been processed. It is probably waiting for other transactions to be batched and compressed.
- MEMPOOL: the transaction has been sent to the node. There is a high probability that the transaction is eventually included in a block, however, it may not be the case!
- CONFIRMED: the transaction has been confirmed on the blockchain.
WARNING Be aware that a reorganization may delete your transaction.
Implementation
embedData(data: [Byte]!, network: Network!): ID
status(uuid: ID!): Status
type StatusResult {
txHash: ID, // Optional
status: EmbedStatus
}
enum EmbedStatus {
PENDING,
MEMPOOL,
CONFIRMED,
}
The implementation requires a database to store the sent transaction, and their status.
- The User (wallet service) calls
embedData(data, network)
- The Writer generates a
uuid
and stores thedata
, theuuid
, thenetwork
, and thestatus=PENDING
in the database. - The User receives the
uuid
that should store in a persistent storage. - The User (wallet service) calls
status(uuid)
. - The Writer returns the status from the database of the transaction.
In the background, the l1_writer
will do the following in a loop (notice, that
these tasks can be done in parallel because they are mutually exclusive tasks):
- Get all PENDING transactions:
- Call
prepare_transaction
for each transaction (or in the future, batch them together and callPreparedTransaction
with the compressed batch). - Add the
PreparedTransaction
to the database row of the pending transaction. - Call
query_or_write_transaction
with thePreparedTransaction
:- Success (
Sent
): update the status to MEMPOOL and insert thetxHash
- Status:
Mempool
andConfirmed
should never happen because westatus=PENDING
is attached to embed tasks that are new or that have failedInvalidPreparedTransaction
(see next bullet point).
- Status:
- Fail (
InvalidPreparedTransaction
): remove thePreparedTransaction
from the database. Wait for the next loop to try again.
- Success (
- Call
- Get all MEMPOOL transactions (these are transactions that have been
Sent
):- Check the status calling
query_or_write_transaction
:- If the status is
InvalidPreparedTransaction
, change the status to PENDING, and set thepreparedTransaction
column tonull
. - If the status is
Sent
/Mempool
, do nothing. The transaction in the mempool will eventually be mined andConfirmed
. - If the status is
Confirmed
, update the status to CONFIRMED.
- If the status is
- Check the status calling
- CONFIRMED transactions will be confirmed forever. If there is a reorganization on the chain and our transaction is removed, it is the responsibility of the client to call embed again.
- (optional) You may (or not) want to eventually delete CONFIRMED transactions.
Optional features
- The service can run in parallel with multiple replicas on k8s. This would require careful treatment of the database rows when processing:
BEGIN;
SELECT uuid, prepared_transaction
FROM table_xxx
WHERE status = PENDING
LIMIT 10
FOR SHARE SKIP LOCKED;
..DO WORK..
..UPDATE (IF NEEDED)..
COMMIT;
-
Batching and compression. The idea is to instead of picking the PENDING transactions one by one, pick a bunch of them (taking into account the size of the payload), and embed them together in a L1 transaction.
-
The challenge with this feature is that it requires updating the method to extract the l2 transaction from the l1 data i.e.
extract_data_p2shdd
. -
This is almost compulsory for Ethereum, where the nonce causes lots of problems in the presence of concurrent transactions.